/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
package org.apache.myfaces.test.webapp.impl;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.apache.myfaces.test.webapp.api.ResourceFinder;
import org.apache.myfaces.test.webapp.impl.metadata.assertion.AssertableELExpression;
import org.apache.myfaces.test.webapp.impl.metadata.assertion.AssertableProxyMethod;
import org.apache.myfaces.test.webapp.impl.metadata.assertion.AssertableValue;
import sun.reflect.Reflection;

import javax.faces.context.FacesContext;
import java.lang.reflect.Method;
import java.util.logging.Logger;

/**
 * @author Cosmin Martinconi, 14.07.2010
 */
public class ProxyInterceptor implements MethodInterceptor {

    private static final Logger log = Logger.getLogger(ProxyInterceptor.class.getName());

    private AssertableProxyMethod methodInvoked;
    private Object methodReturnValue;
    private Class testClass;

    public ProxyInterceptor() {
        methodInvoked = new AssertableProxyMethod();
    }

    public Object newInstance(Class clazz) {
        try {
            Enhancer e = new Enhancer();
            e.setSuperclass(clazz);
            e.setCallback(this);
            return e.create();
        } catch (Throwable e) {
            log.severe("Could not generate ne instance of class " + clazz.getName());
            throw new Error(e.getMessage());
        }
    }

    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

        // if assertThat() or expectCall() method is called an AssertableValue will be injected as argument
        if (WebappTesterImpl.class.isInstance(obj) && ( method.getName().equals("assertThat")
                                                 || method.getName().equals("expectCall"))) {
            AssertableValue assertValue = getAssertableValue(args[0]);

            if (assertValue == null)
                throw new IllegalArgumentException(args[0] + " argument of " + method + " method is not supported.");
            else
                return proxy.invokeSuper(obj, new Object[]{assertValue});
        }

        if (testClass.equals(Reflection.getCallerClass(3))) {
            saveMethodInvoked(obj, args, proxy);

            // calls are not supported on FacesContext since it has no implementation attached yet
            if (FacesContext.class.isAssignableFrom(obj.getClass()))
                return methodReturnValue = null;
            
            methodReturnValue = methodInvoked.evaluate();
            return methodReturnValue;
        }

        return proxy.invokeSuper(obj, args);
    }

    public static ProxyInterceptor getCurrentInstance() {
        return (ProxyInterceptor) ResourceFinder.getResource(ProxyInterceptor.class.getName());
    }

    public void setTestClass(Class tClass) {
        testClass = tClass;
    }

    public AssertableProxyMethod getSavedMethod() {
        return new AssertableProxyMethod(methodInvoked.getMethodObj(),
                                          methodInvoked.getMethodArgs(),
                                          methodInvoked.getMethodProxy());
    }

    private void saveMethodInvoked(Object obj, Object[] args, MethodProxy proxy) {
        methodInvoked.setMethodObj(obj);
        methodInvoked.setMethodArgs(args);
        methodInvoked.setMethodProxy(proxy);
    }

    private AssertableValue getAssertableValue(Object arg) {

        if (arg == methodReturnValue || (arg != null && arg.equals(methodReturnValue))) {
            return getSavedMethod();
        }

        if (String.class.isInstance(arg)) {
            String elValue = (String) arg;

            if (elValue.matches("#\\{.*}")) {
                AssertableELExpression elExpression = new AssertableELExpression();
                elExpression.setELValue(elValue);
                return elExpression;
            }
            else {
                throw new IllegalArgumentException("String value " + elValue + " is not a valid EL expression!");
            }
        }

        return null;
    }

}
